package sample;

import static rescuecore2.misc.Handy.objectsToIDs;

import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.hamcrest.core.IsNull;

import rescuecore2.worldmodel.EntityID;
import rescuecore2.worldmodel.ChangeSet;
import rescuecore2.messages.Command;
import rescuecore2.log.Logger;

import rescuecore2.standard.entities.Blockade;
import rescuecore2.standard.entities.Human;
import rescuecore2.standard.entities.Road;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.Refuge;
import rescuecore2.standard.entities.FireBrigade;
import rescuecore2.standard.entities.StandardWorldModel;
import rescuecore2.standard.messages.AKSpeak;

/**
   A sample fire brigade agent.
 */
@SuppressWarnings("unused")
public class SampleFireBrigade extends AbstractSampleAgent<FireBrigade> {
    private static final String MAX_WATER_KEY = "fire.tank.maximum";
    private static final String MAX_DISTANCE_KEY = "fire.extinguish.max-distance";
    private static final String MAX_POWER_KEY = "fire.extinguish.max-sum";
    private static final String MAX_VIEW_KEY = "perception.los.max-distance";

    private int maxWater;
    private int maxDistance;
    private int maxPower;
    private int maxView;
    private List<EntityID> Destination = new ArrayList<EntityID>();
    private List<Building> Incendios = new ArrayList<Building>();
    private List<Building> IncendiosCombatidos = new ArrayList<Building>();
    private List<EntityID> Vitimas = new ArrayList<EntityID>();
    private int lastPos = -1;
    private Boolean mudarRota = false;
    private Building alvoAtual = null;
    private int Estado;
    private int tentativas;
    
    /*
     * TODO:
     * - Identificar as vítimas
     * - Repassar as informações adiante
     */
    
    
    @Override
    public String toString() {
        return "Sample fire brigade";
    }

    @Override
    protected void postConnect() {
        super.postConnect();
        model.indexClass(StandardEntityURN.BUILDING, StandardEntityURN.REFUGE);
        maxWater = config.getIntValue(MAX_WATER_KEY);
        maxDistance = config.getIntValue(MAX_DISTANCE_KEY);
        maxPower = config.getIntValue(MAX_POWER_KEY);
        maxView = config.getIntValue(MAX_VIEW_KEY);
        //System.out.println("Conectado");
    }

    @Override
    protected void think(int time, ChangeSet changed, Collection<Command> heard) {
    	//System.out.println("#########################PASSO ATUAL - " + time);
    	
        if (time == config.getIntValue(kernel.KernelConstants.IGNORE_AGENT_COMMANDS_KEY)) {
            // Subscribe to channel 1
            sendSubscribe(time, 1);
        }
        
        FireBrigade me = me();
        
        /*
         * Sequencia de Ações:
         * 1 - Olhar o ambiente e atualizar os incêndios
         * 2 - Enviar mensagens
         * 3 - Receber as mensagens e atualizar os incêndios
         * 4 - Verificar se está combatendo algum incendio e se ele já acabou
         * 5 - Avaliar em qual estado o agente se encontra
         */
        
        //1 - Olhar o ambiente e atualizar os incêndios
        //System.out.println(me.getID() + " - Percebendo incendios");
        perceberIncendios();
        
        //2 - Enviar mensagens
        //System.out.println(me.getID() + " - Enviando mensagens");
        enviarMensagem(time);
        
        //3 - Receber as mensagens e atualizar os incêndios
        //System.out.println(me.getID() + " - Recebendo mensagens");
        receberMensagem(heard);
        //if (alvoAtual != null)
        	//System.out.println(me.getID() + " - Meu alvo atual agora é " + alvoAtual.getID());
        //else
        	//System.out.println(me.getID() + " - Meu alvo atual está nulo");

        //4 - Verificar se está combatendo algum incendio e se ele já acabou
        //System.out.println(me.getID() + " - Atualizando alvo atual");
        RemoverAlvoAtual();

        //f (alvoAtual != null)
        	//System.out.println(me.getID() + " - Meu alvo atual depois de atualizar é " + alvoAtual.getID());
        //else
        	//System.out.println(me.getID() + " - Meu alvo atual está nulo");

        /*
         * 5 - Avaliar em qual estado o agente se encontra
         * 
         * Estado:
         * 1 - Recarregando água no refúgio
         * 2 - Sem água, indo até um refúgio
         * 3 - Procurando por focos de incêndio
         * 4 - Pode combater incêndio
         * 5 - Indo combater um incêndio conhecido
         */
        //Identificando o estado do agente
        if (me.isWaterDefined() && me.getWater() < maxWater && location() instanceof Refuge) {
        	Estado = 1;
        } else if (me.isWaterDefined() && me.getWater() == 0) {
        	Estado = 2;
        } else if (Incendios.size() == 0) {
        	Estado = 3;
        } else if (alvoAtual != null && (model.getDistance(me().getPosition(),alvoAtual.getID()) <= maxView)) {
        	Estado = 4;
        } else {
        	Estado = 5;
        }
        
        switch (Estado) {
		case 1:
			//System.out.println(me.getID() + " - Estou no refúgio (" + location() + ") recarregando água.");
			recarregarAgua(time);
			break;
		case 2:
			//System.out.println(me.getID() + " - Estou sem água indo para um refúgio.");
			gotoRefugio(time);
			break;
		case 3:
			//System.out.println(me.getID() + " - Estou procurando por um foco de incendio.");
			procurarIncendios(time);
			break;
		case 4:
			//System.out.println(me.getID() + " - Estou combatendo um incendio.");
			apagarIncendio(time);
			break;
		case 5:
			//System.out.println(me.getID() + " - Verificando se consigo ver o incendio.");
			//Se chegar aqui é porque a lista de incendios está carregada e o alvo atual não está setado
			alvoAtual = getIncendioMaisProximo();
			
			//Verificar se pode apagar o fodo de onde está
			if (model.getDistance(me().getPosition(),alvoAtual.getID()) <= maxView){
				apagarIncendio(time);
			} else {
				gotoIncendio(time);
			}
		}
        return;
    }
    
    private Building getIncendioMaisProximo(){
    	int Distancia = Integer.MAX_VALUE;
    	Building aux = Incendios.get(0);
    	for(Building b : Incendios){
    		if (model.getDistance(me().getPosition(),b.getID()) < Distancia){
    			Distancia = model.getDistance(me().getPosition(),b.getID());
    			aux = b;
    		}
    	}
    	return aux;
    }
    
    private void recarregarAgua(int time){
		sendRest(time);
    }
    
    private void gotoRefugio(int time){
		//Procura o caminho até o refúgio
		Destination = search.breadthFirstSearch(me().getPosition(), refugeIDs);
		
		//Caso não encontre o caminho, aguarda até que a via esteja liberada para então continuar o caminho
		if (Destination != null) {
			//System.out.println(me().getID() + " - Indo para " + Destination.get(Destination.size()-1));
        	MoveTo(time, Destination);
        } else {
            //System.out.println(me().getID() + " - Não encontrou o caminho até o refúgio, aguardando liberação da via.");
            sendRest(time);
        }
    }
    
    private void procurarIncendios(int time){
		//Procurando por focos de incêndios
    	
    	//Está andando aleatoriamente e ainda não chegou a seu destino
    	if(Destination != null && Destination.size() > 0){
    		if (Destination.get(Destination.size()-1) != me().getPosition() && !mudarRota){
        		Destination = plantoDestination(Destination.get(Destination.size()-1));
        		MoveTo(time, Destination);
            	if (lastPos == me().getPosition().getValue()){
            		mudarRota = true;
            	}	
    		} else {
    			gotoAleatorio(time);
    		}
    	} else {
    		gotoAleatorio(time);
    	}
    }
    
    private void gotoAleatorio(int time){
		//Está andando aleatoriamente e já chegou a seu destino
		Destination = randomWalk();
		tentativas = 0;
		//System.out.println(me().getID() + " - Random destination");
		while (Destination == null && tentativas < 3){
			//System.out.println(me().getID() + " - Não encontrou um caminho válido, fazendo mais uma tentativa(" + tentativas + ")");
			tentativas++;
			Destination = randomWalk();
		}
		
		//Caso não encontre o caminho, aguarda até que a via esteja liberada para então continuar o caminho
		if (Destination != null) {
			//System.out.println(me().getID() + " - Indo para " + Destination.get(Destination.size()-1));
        	MoveTo(time, Destination);
        } else {
            //System.out.println(me().getID() + " - Não encontrou nenhum caminho válido, aguardando liberação da via.");
            sendRest(time);
        }
    }
    
    private void apagarIncendio(int time){
        sendExtinguish(time, alvoAtual.getID(), maxPower);
    }
    
    private void gotoIncendio(int time){
    	//Monta o caminho até o incendio
        Destination = search.breadthFirstSearch(me().getPosition(), alvoAtual.getID());
        
        
        
        //Se não encontrar um caminho, fica aguardando
        if (Destination == null){
        	sendRest(time);
        } else {
        	MoveTo(time, Destination);
        }
    }
    
    private Boolean RemoverAlvoAtual(){
    	Collection<StandardEntity> e = model.getEntitiesOfType(StandardEntityURN.BUILDING);
    	List<Building> result = new ArrayList<Building>();
    	Boolean vi = false;
    	
    	for (StandardEntity next : e) {
            if (next instanceof Building) {
                Building b = (Building)next;
                
                if (b.isOnFire()) {
                	if(!b.isTemperatureDefined()){
                		result.add(b);
                	} else {
                		if (b.getTemperature() > 50){
                    		result.add(b);                			
                		}
                	}
                }
            }
        }
    	
    	if(alvoAtual != null){
    		if (model.getDistance(me().getPosition(),alvoAtual.getID()) <= maxView){
            	//System.out.println(me().getID() + " - Encontrei o prédio " + alvoAtual.getID() + " no meu campo de visão.");
    			vi = true;
    		}else{
	        	//System.out.println(me().getID() + " - Não encontrei o prédio " + alvoAtual.getID() + " no meu campo de visão.");
	    	}
	    	
	    	//System.out.println(me().getID() + " - Imprimindo todos os edifícios em chamas do model.");
	    	for(Building b : result){
	    		//System.out.println(me().getID() + " - " + b.getID());
	    	}
	    	
	    	if(!result.contains(alvoAtual) && vi){
	    		//System.out.println(me().getID() + " - Teoricamente eu vi o prédio e ele não está em chamas.");
	    		IncendiosCombatidos.add(alvoAtual);
	    		Incendios.remove(alvoAtual);
	    		alvoAtual = null;
	    		return false;
	    	}else{
	    		return true;
	    	}
    	}else{
    		return true;
    	}
    }
    
    private void MoveTo(int time, List<EntityID> Dest){
        lastPos = me().getPosition().getValue();
        mudarRota = false;
    	sendMove(time, Destination);
    	return;
    }
    
    @Override
    protected List<EntityID> randomWalk() {
    	return search.breadthFirstSearch(me().getPosition(), roadIDs.get(new Random().nextInt(roadIDs.size())));
    }
    
    protected List<EntityID> plantoDestination(EntityID e) {
    	return search.breadthFirstSearch(me().getPosition(), e);
    }
    
    @Override
    protected EnumSet<StandardEntityURN> getRequestedEntityURNsEnum() {
        return EnumSet.of(StandardEntityURN.FIRE_BRIGADE);
    }

    private List<EntityID> planPathToFire(EntityID target) {
        // Try to get to anything within maxDistance of the target
        Collection<StandardEntity> targets = model.getObjectsInRange(target, maxDistance);
        if (targets.isEmpty()) {
            return null;
        }
        return search.breadthFirstSearch(me().getPosition(), objectsToIDs(targets));
    }
    
    private void perceberIncendios(){
    	//Identifica todos os edifícios em seu campo de visão que estão em chamas
        Collection<StandardEntity> all = getBurningBuildings();
        
        if (Incendios.size() > 0 ){
        	//System.out.println(me().getID() + " - Os seguintes incendios são conhecidos:");
        	for (Building b : Incendios){
        		//System.out.println(me().getID() + " - Edificio: " + b.getID());
        	}
        }
        
    	//Verifica se eles já não então inseridos na lista de incendios
    	for(StandardEntity e : all){
    		if (e instanceof Building){
        		//System.out.println(me().getID() + " - Identificou o edificio " + e.getID() + " em chamas");
        		if(!Incendios.contains(e)){
        			Incendios.add((Building)e);
        		}    			
    		}
    	}
    	
    	//System.out.println(me().getID() + " - Tem isso nos incendios:");
    	for(Building b : Incendios){
    		//System.out.println(me().getID() + " - Edificio: " + b.getID());
    	}
    }
    
    private Collection<StandardEntity> getBurningBuildings() {
        Collection<StandardEntity> e = model.getObjectsInRange(location(), maxView);
        List<StandardEntity> result = new ArrayList<StandardEntity>();
        for (StandardEntity next : e) {
            if (next instanceof Building && next != null) {
                Building b = (Building)next;
                if(b.isOnFire()){
	                if (b.getFieryness() < 4) {
	                	//System.out.println("ID: " + b.getID());
	                    result.add(b);
	                }
                }
            }
        }
        return result;
    }
    
    private void enviarMensagem(int time){
        String Mensagem = "";
        if (Incendios.size() > 0){
        	 Mensagem = "#";
             for (Building b : Incendios) {
             	Mensagem += "C" + b.getID();
             }
        }
        
        if(Mensagem.trim().length() > 0){
            //System.out.println(me().getID() + " - Enviando a mensagem: " + Mensagem);
            
            sendSpeak(time, CANAL_BOMBEIROS, Mensagem.getBytes());        	
        }
    }
    
    private void receberMensagem(final Collection<Command> heard){
    	Boolean tem = false;
    	
        //Envia as mensagens dos incendios que reconhece e dos civis soterrados
    	for (final Command next : heard) {
            if (next instanceof AKSpeak) {
            	final String aux = new String(((AKSpeak) next).getContent());
        		//System.out.println(me().getID() + " - RECEBIDO - " + aux);            	
            	if (aux.length() > 0){
            		if(aux.contains("#")){
                		//System.out.println("1 - " + aux);
            			String[] aux2 = aux.split("#");
            			for(String s : aux2){
            				if(s.contains("C")){
            					String[] aux3 = s.split("C");
            					for(String n : aux3){
                            		//System.out.println(me().getID() + " - 2 - " + n);
                            		if (!n.equals("")){
	            						if(n.contains("S")){
	                    					String[] aux4 = s.split("S");
	                    					try{
	                                    		//System.out.println(me().getID() + " - 2 - Adicionou: " + n);
	                                    		Building b = new Building(new EntityID(Integer.parseInt(aux4[0])));
	                                    		if (!Incendios.contains(b) && !IncendiosCombatidos.contains(b))
	                                    			Incendios.add(b);
	                    						tem = true;
	                    					}catch(Exception e){
	                    						//System.out.println(me().getID() + " - Não deve ter nada antes do S.");
	                    						tem = false;
	                    					}
	                    					for(String n2 : aux4){
	                                    		//System.out.println(me().getID() + " - 3 - " + n2);
	                    						if(tem){
	                    							tem = false;
	                    						}else{
	                                        		//System.out.println(me().getID() + " - 3 - Adicionou: " + n2);
	                    							Vitimas.add(new EntityID(Integer.parseInt(n2)));
	                    						}
	                    					}
	            						}else{
	                                		//System.out.println(me().getID() + " - 2 - Adicionou: " + n);
                                    		Building b = new Building(new EntityID(Integer.parseInt(n)));
                                    		if (!Incendios.contains(b) && !IncendiosCombatidos.contains(b))
                                    			Incendios.add(b);
	            						}
                            		}
            					}
            				}
            			}
            		}
            	}
            }
    	}
    }
}
